package com.gitee.wsl.mathematics.coordinate.d3


import com.gitee.wsl.mathematics.geometry.d3.face.FaceKind
import com.gitee.wsl.mathematics.geometry.d3.polygon.ext.validate
import com.gitee.wsl.mathematics.geometry.d3.polygon.ext.validateKinds
import com.gitee.wsl.mathematics.geometry.d3.polygon.seed.Seed
import com.gitee.wsl.mathematics.geometry.d3.polygon.seed.SeedType
import com.gitee.wsl.mathematics.geometry.d3.polygon.seed.Seeds
import com.gitee.wsl.mathematics.geometry.d3.polygon.seed.Tetrahedron
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.Transform
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.TransformFEV
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.Transforms
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.bevelled
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.canonical
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.cantellated
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.drop
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.fev
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.isCanonical
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.rectified
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.snub
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.totalIterations
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.transformed
import com.gitee.wsl.mathematics.geometry.d3.vertex.VertexKind
import kotlin.test.*

class ValidatePolyhedra {
    private val expandingTransforms = Transforms.filter { it.fev != TransformFEV.ID }

    @Test
    fun testRectifiedTetrahedronIsOctahedron() {
        val poly = Seed.Tetrahedron.poly.rectified()
        poly.validate()
        check(poly.faceKinds.size == 1)
        check(poly.edgeKinds.size == 1)
        check(poly.vertexKinds.size == 1)
    }

    @Test
    fun testSnubTetrahedronIsIcosahedron() {
        val poly = Seed.Tetrahedron.poly.snub()
        poly.validate()
        check(poly.faceKinds.size == 1)
        check(poly.edgeKinds.size == 1)
        check(poly.vertexKinds.size == 1)
    }

    @Test
    fun testCantellatedTetrahedronSymmetry() {
        val poly = Seed.Tetrahedron.poly.cantellated()
        poly.validate()
        check(poly.faceKinds.size == 2)
        check(poly.edgeKinds.size == 1)
        check(poly.vertexKinds.size == 1)
    }

    @Test
    fun testBevelledTetrahedronSymmetry() {
        val poly = Seed.Tetrahedron.poly.bevelled()
        poly.validate()
        check(poly.faceKinds.size == 2)
        check(poly.edgeKinds.size == 2)
        check(poly.vertexKinds.size == 1)
    }

    @Test
    fun validateSeeds() {
        testParameter("seed", Seeds) { seed ->
            val poly = seed.poly
            poly.validate()
            when (seed.type) {
                SeedType.Platonic -> {
                    check(poly.faceKinds.size == 1)
                    check(poly.edgeKinds.size == 1)
                    check(poly.vertexKinds.size == 1)
                }
                SeedType.Arhimedean -> {
//                    check(poly.vertexKinds.size == 1)
                }

                SeedType.Catalan -> TODO()
            }
            // check FEV counts
            assertEquals(seed.fev, poly.fev())
        }
    }

    @Test
    fun validatePlatonicTransform() {
        testParameter("seed", Seeds.filter { it.type == SeedType.Platonic} ) { seed ->
            testParameter("transform", expandingTransforms) { transform ->
                println("Checking $transform $seed")
                seed.poly.transformed(transform).validate()
            }
        }
    }

    // Should be able to cantellate any Platonic seed as long as needed
    @Test
    fun validatePlatonicCantellationSequence() {
        testParameter("seed", Seeds.filter { it.type == SeedType.Platonic}) { seed ->
            testParameter("n", 1..5) { n ->
                println("Checking Cantellated^$n $seed")
                seed.poly.transformed(List(n) { Transform.Cantellated }).validate()
            }
        }
    }

/*
    @Test
    fun validatePlatonic2Transforms() {
        testParameter("seed", Seeds.filter { it.type == SeedType.Platonic}) { seed ->
            testParameter("transform1", expandingTransforms) { transform1 ->
                testParameter("transform2", expandingTransforms) { transform2 ->
                    if (isOkSequence(transform1, transform2)) {
                        println("Checking $transform2 $transform1 $seed")
                        seed.poly.transformed(transform1, transform2).validate()
                    }
                }
            }
        }
    }

    @Test
    fun validatePlatonic3Transforms() {
        testParameter("seed", Seeds.filter { it.type == SeedType.Platonic}) { seed ->
            testParameter("transform1", expandingTransforms) { transform1 ->
                testParameter("transform2", expandingTransforms) { transform2 ->
                    testParameter("transform3", expandingTransforms) { transform3 ->
                        if (isOkSequence(transform1, transform2, transform3)) {
                            println("Checking $transform3 $transform2 $transform1 $seed")
                            seed.poly.transformed(transform1, transform2, transform3).validate()
                        }
                    }
                }
            }
        }
    }
    */

    @Test
    fun validateTransformedCanonical() {
        // Note: It fails on Chamfered Triakis icosahedron, so here we test only platonic & arhimedean seeds
        val seedsToTest = Seeds.filter { it.type == SeedType.Platonic || it.type == SeedType.Arhimedean }
        testParameter("seed", seedsToTest) { seed ->
            seed.poly.canonical().validate()
            testParameter("transform", expandingTransforms) next@{ transform ->
                val transformed = seed.poly.transformed(transform)
                if (transformed.isCanonical()) return@next
                try {
                    transformed.validate()
                } catch (e: Exception) { return@next }
                println("Checking Canonical $transform $seed, ${transformed.fev()}")
                transformed.canonical().validate()
            }
        }
        println("Total iterations $totalIterations")
    }

    @Test
    fun testCannotDropRegularVerticesAndFaces() {
        testParameter("seed", Seeds.filter { it.type in listOf(SeedType.Platonic, SeedType.Arhimedean) }) { seed ->
            val poly = seed.poly
            val dropSet = poly.canDrop.filter { it is VertexKind || it is FaceKind }
            assertTrue(dropSet.isEmpty(), "Non empty: $dropSet")
        }
    }

    @Test
    fun testDropComplex() {
        testParameter("seed", Seeds.filter { it.type == SeedType.Platonic }) { seed ->
            testParameter("transform1", expandingTransforms) { transform1 ->
                testParameter("transform2", expandingTransforms) { transform2 ->
                    val poly = seed.poly.transformed(transform1, transform2)
                    if (poly.canDrop.isNotEmpty()) {
                        testParameter("drop", poly.canDrop) { kind ->
                            println("Checking drop($kind) $transform2 $transform1 $seed")
                            poly.drop(kind).validateKinds()
                        }
                    }
                }
            }
        }
    }
}

/*private fun isOkSequence(vararg transforms: Transform): Boolean {
    val t = transforms.toList()
    if (t.getOrNull(0) == Transform.Chamfered && t.getOrNull(1) in listOf(Transform.Chamfered, Transform.Bevelled)) return false
    if (t.lastIndexOf(Transform.Chamfered) >= 0 && t.size > 1) return false // chamfering can be used only with one other
    if (t.lastIndexOf(Transform.Bevelled) > 1) return false // bevelling must be first or second
    if (t.lastIndexOf(Transform.Snub) > 0) return false // Snub must be first
    return true
}*/


fun <T> testParameter(name: String, list: Iterable<T>, block: (T) -> Unit) {
    for (value in list) {
        try {
            block(value)
        } catch (e: Throwable) {
            val msg = e.message
            val cause = if (e is TestParameterException) e.cause!! else e
            val sep = if (e is TestParameterException) "," else ":"
            throw TestParameterException("$name = $value$sep $msg", cause)
        }
    }
}

class TestParameterException(message: String, cause: Throwable) : Exception(message, cause)